package model.objects;

import physics.EuclideanVector;
import java.awt.Image;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;


/**
 * Wrapper for data contained in a level of a game.
 * 
 * @author Sam Rang
 *         modified by geo4
 * 
 */
public class Level {
    private Image myBackground;
    private GameObject myHero;
    private List<GameObject> myGameObjects;
    private Point2D myDimensions;
    private int myScore;
    private boolean myLevelIsWon = false;
    private boolean myLevelIsLost = false;
    private Collection<EuclideanVector> myGlobalForces;

    /**
     * Creates a level given a list of GameObjects in that level.
     * 
     * @param objects list of GameObjects populating the level.
     */
    public Level (List<GameObject> objects) {
        myGameObjects = new ArrayList<GameObject>(objects);
        myDimensions = calculateDimensions(objects);
        myGlobalForces = new ArrayList<EuclideanVector>();
    }

    /**
     * Empty constructor to eliminate circular dependencies.
     */
    public Level () {
        this(new ArrayList<GameObject>());
    }

    /**
     * Updates all the objects in the level.
     * 
     * @param elapsedTime time since last update stored as a double
     */
    public void update (double elapsedTime) {
        EuclideanVector[] forces = new EuclideanVector[myGlobalForces.size()];
        myHero.update(elapsedTime, myGlobalForces.toArray(forces));
        for (GameObject g : myGameObjects) {
            g.update(elapsedTime, myGlobalForces.toArray(forces));
        }
    }

    /**
     * Returns an unmodifiableList of the GameObjects in the Level.
     * 
     * @return unmodifiableList of GameObjects in the level
     */
    public List<GameObject> getObjects () {
        return Collections.unmodifiableList(myGameObjects);
    }

    /**
     * Adds a GameObject other than the hero to the level.
     * 
     * @param addition GameObject being added
     */
    public void add (GameObject addition) {
        myGameObjects.add(addition);
    }

    /**
     * Adds the GameObject representing the controllable "hero" to the level.
     * 
     * @param hero GameObject implementing a HeroMovementStrategy and
     *        HeroCollisionStrategy
     */
    public void addHero (GameObject hero) {
        myHero = hero;
    }

    /**
     * Adds a global force to the level (such as gravity).
     * 
     * @param euclideanVector a euclideanvector representing the strength and
     *        direction of the global force
     */
    public void addGlobalForce (EuclideanVector euclideanVector) {
        myGlobalForces.add(euclideanVector);
    }

    /**
     * Returns a reference to the hero that was added via the addHero(...)
     * method.
     * 
     * @return the GameObject representing the hero
     */
    public GameObject getHero () {
        return myHero;
    }

    /**
     * Gets the location of the hero in the level.
     * 
     * @return the location of the hero GameObject as a Point2D
     */
    public Point2D getHeroLocation () {
        return new Point2D.Double(myHero.getBounds().getX(), myHero.getBounds().getY());
    }

    /**
     * Changes the current score by the amount specified.
     * 
     * @param scoreIncrement amount of points by which to increment the score
     */
    public void incrementScore (int scoreIncrement) {
        myScore = myScore + scoreIncrement;
    }

    /**
     * Called when the hero reaches the level's goal.
     * 
     * @param isWon true if the game was won
     * @param cause message describing how the level was won
     */
    public void setLevelWon (boolean isWon, String cause) {
        myLevelIsWon = isWon;
    }

    /**
     * Called when the hero goes out of the bounds of the level or collides with
     * the side or bottom of an enemy.
     * 
     * @param isLost true if the game was lost
     * @param cause message describing why the level was lost
     */
    public void setLevelLost (boolean isLost, String cause) {
        myLevelIsLost = isLost;
    }

    /**
     * Checks if the level has been beaten.
     * 
     * @return true if the level has been beaten
     */
    public boolean getLevelWon () {
        return myLevelIsWon;
    }

    /**
     * Checks if the the player got a game over (e.g. died, ran out of time,
     * etc.).
     * 
     * @return true if the player got a game over
     */
    public boolean getLevelLost () {
        return myLevelIsLost;
    }

    /**
     * Find the width of the Level, as defined by the edge of the farthest right
     * object when the Level is instantiated.
     * 
     * @return the width of the Level
     */
    public double getWidth () {
        return myDimensions.getX();
    }

    /**
     * Find the height of the Level, as defined by the edge of the highest
     * object when Level is instantiated.
     * 
     * @return
     */
    public double getHeight () {
        return myDimensions.getY();
    }

    /**
     * Finds the maximum dimensions of the Level by finding the farthest right
     * and highest point of all the GameObjects passed to Level during
     * instantiation.
     * 
     * @param gameObjects objects present in the level
     * @return a point represent the maximum width and highest point of all the
     *         GameObjects
     */
    private Point2D calculateDimensions (List<GameObject> gameObjects) {
        double currentMaxWidth = 0.0;
        double currentMaxHeight = 0.0;
        for (GameObject g : myGameObjects) {
            Rectangle2D gRect = g.getBounds();
            if (gRect.getMaxX() > currentMaxWidth) {
                currentMaxWidth = gRect.getMaxX();
            }
            if (gRect.getMaxY() > currentMaxHeight) {
                currentMaxHeight = gRect.getMaxY();
            }
        }
        return new Point2D.Double(currentMaxWidth, currentMaxHeight);
    }
}
